#ifndef PUDGE_GRFX_MESH_HPP
#define PUDGE_GRFX_MESH_HPP

#include <pudge/math/vec.hpp>

namespace pudge { namespace grfx {

	struct STATIC_DRAW
	{	static const GLenum glenum = GL_STATIC_DRAW; };
	
	struct DYNAMIC_DRAW
	{	static const GLenum glenum = GL_DYNAMIC_DRAW; };

	struct RESET_DRAW
	{	static const GLenum glenum = GL_DYNAMIC_DRAW; };

	struct TRIANGLES
	{	static const GLenum glenum = GL_TRIANGLES; };

	struct TRI_STRIP
	{	static const GLenum glenum = GL_TRIANGLE_STRIP; };

	struct QUADS
	{	static const GLenum glenum = GL_QUADS; };
	
	template<class Traits> struct vertex_attribute
	{
		typedef typename Traits::value_type value_type;
		typedef typename Traits::usage_type usage_type;

		GLuint id_;
		GLuint id() const
		{ 
			return id_; 
		}
		void id(const GLuint i) 
		{ 
			id_ = i; 
		}
		vertex_attribute() : id_(-1) {}
	};

	template<class Traits> struct index_attribute : vertex_attribute<Traits> 
	{
		typedef typename Traits::poly_type poly_type;
	};

	// specialize this template for your own types.

	template<class T> struct attribute_meta
	{
		typedef T value_type;
		const static int size = 1;
	};
	template<class T> struct attribute_meta<pudge::math::vec<2,T>>
	{
		typedef T value_type;
		const static int size = 2;
	};
	template<class T> struct attribute_meta<pudge::math::vec<3,T>>
	{
		typedef T value_type;
		const static int size = 3;
	};
	template<class T> struct attribute_meta<pudge::math::vec<4,T>>
	{
		typedef T value_type;
		const static int size = 4;
	};

	template<class T> struct attribute_stream
	{
		typedef T value_type;
		typedef unsigned int uint;

		const uint size_;
		uint size() { return size_; }

		uint mark_;
		uint mark() 
		{ 
			return mark_; 
		}
		void mark(const uint m) 
		{ 
			mark_ = m; 
		}

		T * data_;
		T * data() 
		{
			return data_; 
		}

		attribute_stream(const uint s, T * d)
			:	size_(s)
			,	data_(d)
			,	mark_(0) {}

		attribute_stream<T> & operator << (T & t)
		{
#ifdef _DEBUG
			if(mark_ >= size_) throw -1;
#endif
			data_[mark_++] = t;
			return *this;
		}

		attribute_stream<T> & operator << (T const& t)
		{
#ifdef _DEBUG
			if(mark_ >= size_) throw -1;
#endif
			data_[mark_++] = t;
			return *this;
		}
	};

	struct decache_attribute_
	{
		template<class A> void operator()(A & a) const
		{
			const GLuint i = a.id();
			
			if(i == -1)
				return;

			glDeleteBuffers(1,&i);
			a.id(-1);
		}	
	} const decache_attribute;

	struct cache_attributes_
	{
		typedef unsigned int uint;

		template<GLenum Target> struct map_ 
		{
			template<class A>
			attribute_stream<typename A::value_type> operator() (A & a, const uint cnt) const
			{
				if(a.id() == -1)
				{
					GLuint id;
					glGenBuffers(1, &id);
					a.id(id);
				}

				glBindBuffer(Target, a.id());
				glBufferData
					(	Target
					,	cnt * sizeof(A::value_type)
					,	0
					,	A::usage_type::glenum );

				return attribute_stream<A::value_type>
					(	cnt , reinterpret_cast<A::value_type*>(glMapBuffer(Target,GL_WRITE_ONLY)));
			}
		};

		template<class T> struct premap_ {};
		template<class Traits> struct premap_<vertex_attribute<Traits>>
		{
			typedef attribute_stream<typename Traits::value_type> result_type;
			result_type operator()(vertex_attribute<Traits> & a, const uint index_cnt, const uint vert_cnt) const
			{
				return map_<GL_ARRAY_BUFFER>()(a,vert_cnt);
			}
		};
		template<class Traits> struct premap_<index_attribute<Traits>>
		{
			typedef attribute_stream<typename Traits::value_type> result_type;
			result_type operator()(index_attribute<Traits> & a, const uint index_cnt, const uint vert_cnt) const
			{
				return map_<GL_ELEMENT_ARRAY_BUFFER>()(a,index_cnt);
			}
		};

		template<class Traits> void unmap_(vertex_attribute<Traits> & a) const
		{
			glBindBuffer(GL_ARRAY_BUFFER, a.id());
			glUnmapBuffer(GL_ARRAY_BUFFER);
		}
		template<class Traits> void unmap_(index_attribute<Traits> & a) const
		{
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, a.id());
			glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); 
		}

		template<class A0, class Func> 
		void operator()
			(	A0 & a0
			,	Func & func
			,	const uint index_cnt
			,	const uint vert_cnt ) const
		{
			premap_<A0>::result_type a0stream = premap_<A0>()(a0, index_cnt, vert_cnt);
			func(a0stream);
			unmap_(a0);
		}
		template<class A0, class A1, class Func> 
		void operator()
			(	A0 & a0
			,	A1 & a1
			,	Func & func
			,	const uint index_cnt
			,	const uint vert_cnt ) const
		{
			premap_<A0>::result_type a0stream = premap_<A0>()(a0, index_cnt, vert_cnt);
			premap_<A1>::result_type a1stream = premap_<A1>()(a1, index_cnt, vert_cnt);
			func(a0stream,a1stream);
			unmap_(a0);
			unmap_(a1);
		}
		template<class A0, class A1, class A2, class Func> 
		void operator()
			(	A0 & a0
			,	A1 & a1
			,	A2 & a2
			,	Func & func
			,	const uint index_cnt
			,	const uint vert_cnt ) const
		{
			premap_<A0>::result_type a0stream = premap_<A0>()(a0, index_cnt, vert_cnt);
			premap_<A1>::result_type a1stream = premap_<A1>()(a1, index_cnt, vert_cnt);
			premap_<A2>::result_type a2stream = premap_<A2>()(a2, index_cnt, vert_cnt);
			func(a0stream,a1stream,a2stream);
			unmap_(a0);
			unmap_(a1);
			unmap_(a2);
		}
		template<class A0, class A1, class A2, class A3, class Func> 
		void operator()
			(	A0 & a0
			,	A1 & a1
			,	A2 & a2
			,	A3 & a3
			,	Func & func
			,	const uint index_cnt
			,	const uint vert_cnt ) const
		{
			premap_<A0>::result_type a0stream = premap_<A0>()(a0, index_cnt, vert_cnt);
			premap_<A1>::result_type a1stream = premap_<A1>()(a1, index_cnt, vert_cnt);
			premap_<A2>::result_type a2stream = premap_<A2>()(a2, index_cnt, vert_cnt);
			premap_<A3>::result_type a3stream = premap_<A3>()(a3, index_cnt, vert_cnt);
			func(a0stream,a1stream,a2stream,a3stream);
			unmap_(a0);
			unmap_(a1);
			unmap_(a2);
			unmap_(a3);
		}
	} const cache_attributes;
}}

#endif